package com.code.train.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.code.train.domain.SkToken;
import com.code.train.domain.SkTokenExample;
import com.code.train.enums.RedisKeyPreEnum;
import com.code.train.mapper.SkTokenMapper;
import com.code.train.mapper.cust.SkTokenMapperCust;
import com.code.train.req.SkTokenQueryReq;
import com.code.train.req.SkTokenSaveReq;
import com.code.train.resp.PageResp;
import com.code.train.resp.SkTokenQueryResp;
import com.code.train.service.DailyTrainSeatService;
import com.code.train.service.DailyTrainStationService;
import com.code.train.service.SkTokenService;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class SkTokenServiceImpl implements SkTokenService {

    @Autowired
    private SkTokenMapper skTokenMapper;

    @Autowired
    private SkTokenMapperCust skTokenMapperCust;

    @Autowired
    private DailyTrainSeatService dailyTrainSeatService;

    @Autowired
    private DailyTrainStationService dailyTrainStationService;

    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public void save(SkTokenSaveReq req) {
        DateTime now = DateTime.now();
        SkToken skToken = BeanUtil.copyProperties(req, SkToken.class);
        if (ObjectUtil.isNull(skToken.getId())) {
            skToken.setId(System.currentTimeMillis());
            skToken.setCreateTime(now);
            skToken.setUpdateTime(now);
            skTokenMapper.insert(skToken);
        } else {
            skToken.setUpdateTime(now);
            skTokenMapper.updateByPrimaryKey(skToken);
        }
    }

    @Override
    public PageResp<SkTokenQueryResp> queryList(SkTokenQueryReq req) {
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.setOrderByClause("id desc");
        SkTokenExample.Criteria criteria = skTokenExample.createCriteria();

        log.info("查询页码：{}", req.getPage());
        log.info("每页条数：{}", req.getSize());
        PageHelper.startPage(req.getPage(), req.getSize());
        List<SkToken> skTokenList = skTokenMapper.selectByExample(skTokenExample);

        PageInfo<SkToken> pageInfo = new PageInfo<>(skTokenList);
        log.info("总行数：{}", pageInfo.getTotal());
        log.info("总页数：{}", pageInfo.getPages());

        List<SkTokenQueryResp> list = skTokenList.stream()
                .map(skToken -> {
                    SkTokenQueryResp resp = new SkTokenQueryResp();
                    BeanUtils.copyProperties(skToken, resp);
                    return resp;
                })
                .collect(Collectors.toList());

        PageResp<SkTokenQueryResp> pageResp = new PageResp<>();
        pageResp.setTotal(pageInfo.getTotal());
        pageResp.setList(list);
        return pageResp;
    }

    @Override
    public void delete(Long id) {
        skTokenMapper.deleteByPrimaryKey(id);
    }

    @Override
    public void genDaily( Date date, String trainCode) {
        log.info("生成日期【{}】车次【{}】的令牌信息开始", DateUtil.formatDate(date), trainCode);

        //删除车次的令牌信息
        SkTokenExample skTokenExample = new SkTokenExample();
        skTokenExample.createCriteria()
                .andDateEqualTo(date)
                .andTrainCodeEqualTo(trainCode);
        skTokenMapper.deleteByExample(skTokenExample);

        //封装数据
        DateTime now = DateTime.now();
        SkToken skToken = new SkToken();
        skToken.setId(IdUtil.getSnowflake(1, 1).nextId());
        skToken.setCreateTime(now);
        skToken.setUpdateTime(now);
        skToken.setDate(date);
        skToken.setTrainCode(trainCode);

        //获取令牌大闸的上限
        //获取同时期车次的座位数
        int countSeat = dailyTrainSeatService.countSeat(trainCode, date);
        log.info("同时期车次【{}】的座位数：{}", trainCode, countSeat);
        //获取同时期车次的车站数
        long countStation = dailyTrainStationService.countByTrainCode(trainCode, date);
        log.info("同时期车次【{}】的车站数：{}", trainCode, countStation);
        //最大令牌数 3/4需要根据实际卖票比例来定，一趟火车最多可以卖（seatCount * stationCount）张火车票
        int max = (int)(countSeat * countStation);//*3/4
        log.info("同时期车次【{}】的最大令牌数：{}", trainCode, max);
        skToken.setCount(max);

        log.info("生成日期【{}】车次【{}】的令牌信息结束", DateUtil.formatDate(date), trainCode);

        skTokenMapper.insert(skToken);
    }

    @Override
    public Boolean valid(Date date, String trainCode,Long memberID) {
        //此处是为了防止机器人刷票
        String lockKey = RedisKeyPreEnum.SK_TOKEN.getCode() + "-" + DateUtil.formatDate(date) + "-" + trainCode;
        //设置五秒
        Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(lockKey, memberID.toString(), 5, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(setIfAbsent)) {
            log.info("恭喜，抢到令牌锁了！lockKey：{}", lockKey);
        } else {
            log.info("很遗憾，没抢到令牌锁！lockKey：{}", lockKey);
            return false;
        }

        String skTokenCountKey = RedisKeyPreEnum.SK_TOKEN_COUNT + "-" + DateUtil.formatDate(date) + "-" + trainCode;
        Object skTokenCount = redisTemplate.opsForValue().get(skTokenCountKey);
        if (skTokenCount != null) {
            log.info("缓存中有该车次令牌大闸的key：{}", skTokenCountKey);
            Long count = redisTemplate.opsForValue().decrement(skTokenCountKey, 1);
            if (count < 0L) {
                log.error("获取令牌失败：{}", skTokenCountKey);
                return false;
            } else {
                log.info("获取令牌后，令牌余数：{}", count);
                redisTemplate.expire(skTokenCountKey, 60, TimeUnit.SECONDS);
                // 每获取5个令牌更新一次数据库
                if (count % 5 == 0) {
                    skTokenMapperCust.decrease(date, trainCode, 5);
                }
                return true;
            }
        } else {
            log.info("缓存中没有该车次令牌大闸的key：{}", skTokenCountKey);
            // 检查是否还有令牌
            SkTokenExample skTokenExample = new SkTokenExample();
            skTokenExample.createCriteria().andDateEqualTo(date).andTrainCodeEqualTo(trainCode);
            List<SkToken> tokenCountList = skTokenMapper.selectByExample(skTokenExample);
            if (CollUtil.isEmpty(tokenCountList)) {
                log.info("找不到日期【{}】车次【{}】的令牌记录", DateUtil.formatDate(date), trainCode);
                return false;
            }

            SkToken skToken = tokenCountList.get(0);
            if (skToken.getCount() <= 0) {
                log.info("日期【{}】车次【{}】的令牌余量为0", DateUtil.formatDate(date), trainCode);
                return false;
            }

            // 令牌还有余量
            // 令牌余数-1
            Integer count = skToken.getCount() - 1;
            skToken.setCount(count);
            log.info("将该车次令牌大闸放入缓存中，key: {}， count: {}", skTokenCountKey, count);
            // 不需要更新数据库，只要放缓存即可
            redisTemplate.opsForValue().set(skTokenCountKey, String.valueOf(count), 60, TimeUnit.SECONDS);
            // skTokenMapper.updateByPrimaryKey(skToken);
            return true;
        }


//         //令牌约等于库存，令牌没有了，就不再卖票，不需要再进入购票主流程去判断库存，判断令牌肯定比判断库存效率高
//         int updateCount = skTokenMapperCust.decrease(date, trainCode, 1);
//         if (updateCount > 0) {
//             return true;
//         } else {
//             return false;
//         }
    }
}